/*******************************************************************************
 * Copyright (c) 2007 University of Oslo.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     University of Oslo - initial API and implementation
 *******************************************************************************/

package com.cea.papyrus.diagram.sequence.sedi.edit;

import static com.cea.papyrus.diagram.sequence.sedi.model.util.ModelUtil.getBodies;

import java.util.Iterator;
import java.util.List;

import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.util.EList;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.Request;
import org.eclipse.gef.RequestConstants;
import org.eclipse.uml2.uml.Constraint;
import org.eclipse.uml2.uml.Lifeline;
import org.eclipse.uml2.uml.OpaqueExpression;
import org.eclipse.uml2.uml.StateInvariant;
import org.eclipse.uml2.uml.UMLPackage;
import org.eclipse.uml2.uml.ValueSpecification;

import com.cea.papyrus.core.editor.ModelManager;
import com.cea.papyrus.core.utils.Debug;
import com.cea.papyrus.core.utils.LookForElement;
import com.cea.papyrus.diagram.sequence.sedi.edit.handlers.StateInvariantLayoutHandler;
import com.cea.papyrus.diagram.sequence.sedi.edit.manager.StateInvariantEditManager;
import com.cea.papyrus.diagram.sequence.sedi.edit.policy.DeletionEditPolicy;
import com.cea.papyrus.diagram.sequence.sedi.edit.policy.StateInvariantDirectEditPolicy;
import com.cea.papyrus.diagram.sequence.sedi.figures.ILabeledFigure;
import com.cea.papyrus.diagram.sequence.sedi.figures.InteractionFragmentFigure1;
import com.cea.papyrus.diagram.sequence.sedi.figures.StateInvariantFigure;
import com.cea.papyrus.diagram.sequence.sedi.model.command.diagram.HintRegister;
import com.cea.papyrus.diagram.sequence.sedi.model.util.ModelUtil;
import com.cea.papyrus.diagraminterchange2.di2.Diagram;
import com.cea.papyrus.diagraminterchange2.di2.GraphElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;

/**
 * Edit part for State Invariants.
 * 
 * @author Andreas Limyr
 * @author frankd@ifi.uio.no
 */
public class StateInvariantEditPart extends SeDiSubpartEditPart<StateInvariant> {

	/**
	 * The {@link Diagram} that contains the {@link StateInvariant}
	 */
	private Diagram diagram; 
	
    public StateInvariantEditPart(GraphElement view, ModelManager modelManager) {
        super(view, modelManager);
        // Get diagram on EditPart creation instead of calling getDiagram
        // dynamically
        // Dynamic call is problematic during deletion (from Model or Diagram)
        // because the graphical container may be null.
        diagram = LookForElement.getDiagram(getGraphNode());
    }

    @Override
    protected IFigure createFigure() {
        return new StateInvariantFigure();
    }

    @Override
    protected void createEditPolicies() {
        super.createEditPolicies();

        // override the default NamedElement policy with a specialized policy for StateInv
        installEditPolicy(EditPolicy.DIRECT_EDIT_ROLE, new StateInvariantDirectEditPolicy());

        // Add edit policy to handle deletion requests (replace SeDiSubpartEditPolicy)
		installEditPolicy(
				EditPolicy.COMPONENT_ROLE,
		        new DeletionEditPolicy());
    }

    @Override
    protected void performDirectEdit(final Request request) {
        new StateInvariantEditManager(this, getCellEditorLocator()).show();
    }

    @Override
    public boolean understandsRequest(Request req) {
        // Extra condition for state invariants: we only allow direct editing
        // when we understand the data structure. We require the StateInvariant
        // to have an invariant Constraint with a Specification of type
        // OpaqueExpression. It is the Body value of the OpaqueExpression
        // that is edited.
        if (RequestConstants.REQ_DIRECT_EDIT.equals(req.getType())) {
            Constraint c = getUmlElement().getInvariant();
            if (c != null) {
                ValueSpecification s = c.getSpecification();
                return (s != null && s instanceof OpaqueExpression);
            }
            return false;
        } else
            return super.understandsRequest(req);
    }

    @SuppressWarnings("unchecked")
    protected void refreshVisualsBounds() {
        GraphNode model = (GraphNode) getModel();
        InteractionFragmentFigure1 figure = (InteractionFragmentFigure1) getFigure();

        final Rectangle bounds = new Rectangle(model.getPosition(), model.getSize());

        final Rectangle coveredBounds;
        List coveredList = getUmlElement().getCovereds();
        Lifeline covered = (coveredList.isEmpty() ? null : (Lifeline) coveredList.get(0));
        if (covered != null) {
            GraphNode coveredView =
                    ModelUtil.getGraphNode(covered, diagram);
            if (coveredView == null) {
            	// The covered Lifeline GraphNode is not yet created
            	// ... abort refreshVisualsBounds
            	return;
            }
            coveredBounds = new Rectangle(coveredView.getPosition(), coveredView.getSize());
        } else
            coveredBounds = null;

        final Rectangle preferred = figure.getPreferredFigureBounds(bounds, coveredBounds);

        if (getParent() != null) // may be null: when the EP is removed
            ((GraphicalEditPart) getParent()).setLayoutConstraint(this, figure, preferred);
        figure.setBounds(preferred);
    }

    @Override
    protected void refreshVisualsLabel() {
        String label = "<Unsupported expression>";
        StateInvariant stateInvariant = getUmlElement();
        Constraint c = stateInvariant.getInvariant();
        if (c != null) {
            ValueSpecification s = c.getSpecification();
            if (s instanceof OpaqueExpression) {
                List<String> bodies = getBodies((OpaqueExpression) s);
                if (!bodies.isEmpty())
                    label = bodies.get(0);
            }
        }
        ILabeledFigure figure = (ILabeledFigure) getFigure();
        figure.setLabel(label);
    }

    /**
     * Retrieve covered {@link Lifeline}
     * @return the covered {@link Lifeline} or null (when the {@link Lifeline} is deleted)
     */
    private Lifeline getCoveredLifeline() {

      // Try to retrieve the covered Lifeline
      final StateInvariant stateInv = getUmlElement();
      Lifeline lifeline = null;
      
      EList<Lifeline> covereds = stateInv.getCovereds();
      if (covereds.isEmpty()) {
      	// No covered Lifeline found...
      	return null;
      }
      
      // Only the fist covered Lifeline is considered
      // This might be a problem if more than one Lifeline is covered...
      // ... is this correct considering UML specification ?
      lifeline = covereds.get(0);

      return lifeline;
    }
    
    private GraphNode getLifelineView(final Lifeline lifeline) {

        GraphNode lifelineView = null;
        if (lifeline != null) {
            GraphElement ge = (GraphElement) getModel();
            if (ge.getContainer() != null) {
                lifelineView = ModelUtil.getGraphNode(lifeline, diagram);
            }
        }

        return lifelineView;
    }
    
    @SuppressWarnings("unchecked")
    @Override
    protected void umlModelChanged(Notification evt) {
        switch (evt.getFeatureID(UMLPackage.class)) {
        case UMLPackage.STATE_INVARIANT__INVARIANT:
        case UMLPackage.CONSTRAINT__SPECIFICATION:
            // =case UMLPackage.OPAQUE_EXPRESSION__BODY:
//            deactivate(evt.getOldValue());
//            activate(evt.getNewValue());
//            refreshVisuals();
//            break;

        case UMLPackage.STATE_INVARIANT__COVERED: {
            switch (evt.getEventType()) {
            case Notification.ADD:
            case Notification.ADD_MANY:
            case Notification.MOVE:
                Rectangle preferredBounds;

                final GraphNode model = (GraphNode) getModel();
                final Rectangle currentBounds = new Rectangle(model.getPosition(), model.getSize());
                final Rectangle hint = (Rectangle) HintRegister.consumeHint("bounds");
                if (hint != null) {

                    final Lifeline covered;
                    if (evt.getNewValue() instanceof Lifeline)
                        covered = (Lifeline) evt.getNewValue();
                    else {
                        List coveredList = (List) evt.getNewValue();
                        covered = (coveredList.isEmpty() ? null : (Lifeline) coveredList.get(0));
                    }

                    if (covered != null) {
                        final StateInvariantFigure figure = (StateInvariantFigure) getFigure();
                        final GraphNode lifelineView =
                                ModelUtil.getGraphNode(covered, diagram);
                        Rectangle bounds =
                                new Rectangle(lifelineView.getPosition(), lifelineView.getSize());
                        preferredBounds = figure.getPreferredModelBounds(hint, bounds);
                    } else
                        preferredBounds = currentBounds; // TODO Warn?
                } else {
                    preferredBounds =
                            new StateInvariantLayoutHandler().layout(diagram, ModelUtil
                                    .<StateInvariant> getSemantic(model));
                }

                if (!preferredBounds.equals(currentBounds)) {
                    model.setSize(preferredBounds.getSize());
                    model.setPosition(preferredBounds.getLocation());
                }

                // refreshVisuals();
            }
            break;
        }

        default:
            super.umlModelChanged(evt);
        }
    }
    
    // --- Activation ---
    @SuppressWarnings("unchecked")
    @Override
    public void activate() {
		if (this.isActive()) {
			return;
		}
		super.activate();
		// start listening for changes in the model
		((Notifier)this.getModel()).eAdapters().add(graphElementListener);
		
		// Add listener on covered Lifeline GraphNode to detect changes
		Lifeline lifeline = getCoveredLifeline();
		if (lifeline != null) {
			GraphNode lifelineGraphNode = getLifelineView(lifeline);
			if (lifelineGraphNode != null) {
				((Notifier) lifelineGraphNode).eAdapters().add(graphElementListener);
			} else {
				Debug.log(new Exception("GraphNode not found for covered lifeline."));
			}
		} else {
			Debug.log(new Exception("Covered Lifeline not found."));
		}
		
		Iterator propertiesIterator= ((GraphNode) this.getModel()).getProperty().iterator();
		while(propertiesIterator.hasNext()){
			((Notifier)propertiesIterator.next()).eAdapters().add(graphElementListener);
		}
    }

    @Override
    public void deactivate() {
		if (!this.isActive()) {
			return;
		}

		// stop listening for changes in the model
		((Notifier)this.getModel()).eAdapters().remove(graphElementListener);
		
		// Remove change listener on covered Lifeline GraphNode if the Lifeline GraphNode still exists
		Lifeline lifeline = getCoveredLifeline();
		if (lifeline != null) {
			GraphNode lifelineGraphNode = getLifelineView(lifeline);
			if (lifelineGraphNode != null) {
				((Notifier) lifelineGraphNode).eAdapters().remove(graphElementListener);
			}
		}
		
		Iterator propertiesIterator= ((GraphNode) this.getModel()).getProperty().iterator();
		while(propertiesIterator.hasNext()){
			((Notifier) propertiesIterator.next()).eAdapters().remove(graphElementListener);
		}		
		
		super.deactivate();
    }
    

//    // --- Activation ---
//
//    @Override
//    public void activate() {
//        super.activate();
//        Constraint constraint = getUmlElement().getInvariant();
//        activate(constraint);
//    }
//
//    @SuppressWarnings("unchecked")
//    private void activate(final Object o) {
//        if (o instanceof Constraint) {
//            Constraint constraint = (Constraint) o;
//            constraint.eAdapters().add(umlAdapter);
//            activate(constraint.getSpecification());
//        } else if (o instanceof ValueSpecification) {
//            ValueSpecification valueSpec = (ValueSpecification) o;
//            valueSpec.eAdapters().add(umlAdapter);
//        }
//    }
//
//    @Override
//    public void deactivate() {
//        deactivate(getUmlElement().getInvariant());
//        super.deactivate();
//    }
//
//    private void deactivate(final Object o) {
//        if (o instanceof Constraint) {
//            Constraint constraint = (Constraint) o;
//            constraint.eAdapters().remove(umlAdapter);
//            deactivate(constraint.getSpecification());
//        } else if (o instanceof ValueSpecification) {
//            ValueSpecification valueSpec = (ValueSpecification) o;
//            valueSpec.eAdapters().remove(umlAdapter);
//        }
//    }
}
